package ru.dc;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ru.dc.object.*;
import ru.dc.object.events.manager.AddConnection;
import ru.dc.object.events.manager.ConnectionManagerEvent;
import ru.dc.object.events.manager.RemoveConnection;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: deady
 * Date: 08.10.2010
 * Time: 23:43:54
 */
public class ConnectionManager {

    private Map<ServerInfo, Connection> connectionsByServerInfo = new HashMap<ServerInfo, Connection>();
    //карта: ip:port -> соединение
    private Map<String, ServerInfo> infoByAddress = new HashMap<String, ServerInfo>();
    private List<Connection> connections = new ArrayList<Connection>();
    private List<ConnectionManagerEventListener> eventListeners = new ArrayList<ConnectionManagerEventListener>();
    private Log logger = LogFactory.getLog(getClass());

    public ConnectionManager() {
    }


    public void addHubConnection(ServerInfo serverInfo) {
        if (connectionsByServerInfo.get(serverInfo)!=null) {
            return;
        }

        //dto pattern
        ServerInfo real = new ServerInfo();
        real.setAddress(serverInfo.getAddress());
        real.setPort(serverInfo.getPort()>0 ? serverInfo.getPort() : 411);

        if (StringUtils.isEmpty(serverInfo.getNick())) {
            real.setNick(Application.getApplication().getSettings().getNick());
        }
        else {
            real.setNick(serverInfo.getNick());
        }

        if (StringUtils.isEmpty(serverInfo.getEncoding())) {
            real.setEncoding(Application.getApplication().getSettings().getEncoding());
        }
        else {
            real.setEncoding(serverInfo.getEncoding());
        }
        real.setActive(Application.getApplication().getSettings().getMode()== Settings.MODE_ACTIVE);

        final Connection connection = new HubConnection();
        connection.setServerInfo(real);
        connections.add(connection);
        try {
            InetAddress addr = InetAddress.getByName(real.getAddress());
            infoByAddress.put(addr.getHostAddress()+":"+real.getPort(), real);
        } catch (UnknownHostException e) {
            logger.warn("Can't get ip of address: "+real.getAddress(), e);
        }
        connectionsByServerInfo.put(real, connection);
        final AddConnection connection1 = new AddConnection(real);
        connection1.setHubConnection(true);
        fireEventOccured(connection1);

        new Thread() {
            @Override
            public void run() {
                try {
                    connection.connect();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }.start();
    }

    public void addClientConnection(final ServerInfo serverInfo, ServerInfo hubInfo) {

        if (connectionsByServerInfo.get(serverInfo)!=null) {
            return;
        }

        final ClientConnection connection = new ClientConnection();
        connection.setNick(hubInfo.getNick());
        connection.setHubInfo(hubInfo);
        connection.setServerInfo(serverInfo);
        connections.add(connection);
        connectionsByServerInfo.put(serverInfo, connection);
        fireEventOccured(new AddConnection(serverInfo));

        new Thread() {
            @Override
            public void run() {
                try {
                    connection.connect();
                } catch (IOException e1) {
                    removeConnection(serverInfo);
                }
            }
        }.start();
    }

    public String getHubName(ServerInfo serverInfo) {
        final Connection connection = findHubConnection(serverInfo);
        if (connection!=null) {
            return ((HubConnection) connection).getName();
        }
        else {
            return serverInfo.getAddress();
        }
    }

    /**
     * Возвращает информацию о соединении по адресу.
     * @param address адрес соединения, в формате <ip>:<port>, например, "100.200.213.124:1234"
     * @return информация о соединении, или null, если такого соединения не найдено
     */
    public ServerInfo findConnectionByAddress(String address) {
        return infoByAddress.get(address);
    }


    public void removeConnection(ServerInfo serverInfo) {
        final Connection connection = findConnection(serverInfo);
        if (connection!=null) {
            new Thread() {
                @Override
                public void run() {
                    connection.close();
                }
            }.start();
            connections.remove(connection);
            connectionsByServerInfo.remove(connection.getServerInfo());
            fireEventOccured(new RemoveConnection(connection.getServerInfo()));
        }
    }

    public void fireEventOccured(ConnectionManagerEvent event) {
        for (ConnectionManagerEventListener listener : eventListeners) {
            listener.onEvent(event);
        }
    }

    public void addEventListener(ConnectionManagerEventListener listener) {
        eventListeners.add(listener);
    }

    public Collection<ServerInfo> enumerateHubConnections() {
        Set<ServerInfo> res = new HashSet<ServerInfo>();
        for (Connection c : connections) {
            if (c instanceof HubConnection) {
                res.add(c.getServerInfo());
            }
        }
        return res;
    }


    public void search(ServerInfo serverInfo, SearchRequest search) {
        HubConnection conn = findHubConnection(serverInfo);
        if (conn!=null) {
            conn.search(search);
        }
    }


    public void chat(ServerInfo serverInfo, String message) {
        HubConnection conn = findHubConnection(serverInfo);
        if (conn!=null) {
            conn.chat(message);
        }
    }

    public void privateChat(ServerInfo serverInfo, String targetNick, String message) {
        HubConnection conn = findHubConnection(serverInfo);
        if (conn!=null) {
            conn.privateChat(targetNick, message);
        }
    }

    public void askForPassiveConnection(DownloadSource source) {
        HubConnection conn = findHubConnection(source.getHub());
        if (conn!=null) {
            conn.askForPassiveConnection(source.getNick());
        }
    }

    public void askForActiveConnection(DownloadSource source) {
        HubConnection conn = findHubConnection(source.getHub());
        if (conn!=null) {
            conn.askForActiveConnection(source.getNick());
        }
    }

    public String getName(ServerInfo serverInfo) {
        HubConnection conn = findHubConnection(serverInfo);
        if (conn!=null) {
            return conn.getName();
        }
        return null;
    }

    public String getTopic(ServerInfo serverInfo) {
        HubConnection conn = findHubConnection(serverInfo);
        if (conn!=null) {
            return conn.getTopic();
        }
        return null;
    }

    public Collection<UserInfo> getUsers(ServerInfo serverInfo) {
        HubConnection conn = findHubConnection(serverInfo);
        if (conn!=null) {
            return conn.getUsers();
        }
        return null;
    }

    private Connection findConnection(ServerInfo serverInfo) {
        return connectionsByServerInfo.get(serverInfo);
    }

    private HubConnection findHubConnection(ServerInfo serverInfo) {
        for (Connection conn : connections) {
            if (conn instanceof HubConnection && conn.getServerInfo()!=null &&
                    conn.getServerInfo().getAddress().equals(serverInfo.getAddress()) &&
                    conn.getServerInfo().getPort()==serverInfo.getPort()) {
                return (HubConnection) conn;
            }
        }
        return null;
    }

    public void reconnect(ServerInfo serverInfo) {
        HubConnection c = (HubConnection) findConnection(serverInfo);
        if (c!=null) {
            try {
                c.reconnect();
            } catch (IOException e) {
                logger.error("Error while reconnecting "+serverInfo, e);
            }
        }
    }

    /**
     * Возвращает хаб, на котором зарегистрирован пользователь с данным ником.
     * @param remoteNick ник пользователя
     * @return пользователь, или null, если ни на одном из хабов нет пользователя с таким ником.
     */
    public ServerInfo findUser(String remoteNick) {
        for (Connection c : connections){
            if (c instanceof HubConnection) {
                if (((HubConnection) c).findUser(remoteNick)!=null) {
                    return c.getServerInfo();
                }
            }
        }
        return null;
    }
}
